/*
 * Alloy Analyzer 4 -- Copyright (c) 2006-2008, Felix Chang
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package edu.mit.csail.sdg.alloy4;

import java.io.IOException;
import java.io.File;
import java.io.RandomAccessFile;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;

/**
 * Graphical convenience methods for producing PNG files.
 */

public final strictfp class OurPNGWriter {

    /** The constructor is private, since this utility class never needs to be instantiated. */
    private OurPNGWriter () { }

    /** Writes the image as a PNG file with the given horizontal and vertical dots-per-inch. */
    public static void writePNG (BufferedImage image, String filename, double dpiX, double dpiY) throws IOException {
        ImageIO.write(image, "PNG", new File(filename));
        setDPI(filename, dpiX, dpiY);
    }

    /*
     * PNG consists of a "8 byte header" followed by one or more CHUNK.
     *
     * Each CHUNK:
     * ===========
     * 4 bytes: N (most-significant-byte first)
     * 4 bytes: Chunk Type
     * N bytes: Chunk Data
     * 4 bytes: Checksum (this checksum is computed over the Chunk Type and Chunk Data)
     *
     * Each PNG must contain an IDAT chunk (this is the actual pixels of the image)
     *
     * Each PNG may contain an optional pHYs chunk that describes the horizontal and vertical dots-per-meter information.
     * If such a chunk exists, it must come before (though not necessarily immediately before) the IDAT chunk.
     *
     * pHYs CHUNK:
     * ===========
     * 4 bytes:  0  ,  0  ,  0  ,  9
     * 4 bytes: 'p' , 'H' , 'Y' , 's'
     * 4 bytes: horizontal dots per meter  (most-significant-byte first)
     * 4 bytes: vertical dots per meter    (most-significant-byte first)
     * 1 byte:   1
     * 4 bytes: Checksum
     */

    /** Modifies the given PNG file to have the given horizontal and vertical dots-per-inch. */
    private static void setDPI (String filename, double dpiX, double dpiY) throws IOException {
       RandomAccessFile f = null;
       try {
          f = new RandomAccessFile(filename, "rw");
          long total = f.length(), pos = 8;
          // Jump to the appropriate place for inserting the pHYs chunk, then insert it
          while(true) {
             if (pos>=total) throw new IOException("PNG is missing the IDAT chunk.");
             f.seek(pos);
             int a1=f.read(), a2=f.read(), a3=f.read(), a4=f.read(), b1=f.read(), b2=f.read(), b3=f.read(), b4=f.read();
             if (a1<0 || a2<0 || a3<0 || a4<0 || b1<0 || b2<0 || b3<0 || b4<0) throw new IOException("PNG is missing the IDAT chunk.");
             int jump = (a1<<24) | (a2<<16) | (a3<<8) | a4;
             if (jump<0 || (total-pos)-12<jump) throw new IOException("PNG chunk size exceeds the rest of the file.");
             if ((b1=='I' && b2=='D' && b3=='A' && b4=='T') || (b1=='p' && b2=='H' && b3=='Y' && b4=='s')) {
                Util.shift(f, (b1=='p' ? (pos+12+jump) : pos), pos+21); // skip over the existing pHYs chunk if we see it
                f.seek(pos);
                writeDPI(f, dpiX, dpiY);
                f.close();
                f=null;
                break;
             }
             pos = pos + 12 + jump;
          }
       } finally {
          Util.close(f);
       }
    }

    /** Write a "pHYs" chunk into the PNG file with the given horizontal and vertical dots-per-inch. */
    private static void writeDPI (RandomAccessFile file, double dpiX, double dpiY) throws IOException {
        int x = (int) (dpiX/0.0254d), y = (int) (dpiY/0.0254d); // Translate dots-per-inch into dots-per-meter
        writeChunk(file, new int[] {'p', 'H', 'Y', 's', x>>>24, x>>>16, x>>>8, x, y>>>24, y>>>16, y>>>8, y, 1});
    }

    /** Write the given chunk into the PNG file;  Note: data.length must be at least 4. */
    private static void writeChunk (RandomAccessFile file, int[] data) throws IOException {
        int crc = (-1), len = data.length-4;
        file.write((len>>>24)&255); file.write((len>>>16)&255); file.write((len>>>8)&255); file.write(len&255);
        for(int i=0; i<data.length; i++) { int x=data[i]; crc=table[(crc ^ x) & 255] ^ (crc >>> 8); file.write(x & 255); }
        crc = crc ^ (-1);
        file.write((crc>>>24)&255); file.write((crc>>>16)&255); file.write((crc>>>8)&255); file.write(crc&255);
    }

    /** This precomputed table makes it faster to calculate CRC; this is based on the suggestion in the PNG specification. */
    private static final int[] table = new int[] {
        0,1996959894,-301047508,-1727442502,124634137,1886057615,-379345611,-1637575261,249268274
        ,2044508324,-522852066,-1747789432,162941995,2125561021,-407360249,-1866523247,498536548,1789927666
        ,-205950648,-2067906082,450548861,1843258603,-187386543,-2083289657,325883990,1684777152,-43845254
        ,-1973040660,335633487,1661365465,-99664541,-1928851979,997073096,1281953886,-715111964,-1570279054
        ,1006888145,1258607687,-770865667,-1526024853,901097722,1119000684,-608450090,-1396901568,853044451
        ,1172266101,-589951537,-1412350631,651767980,1373503546,-925412992,-1076862698,565507253,1454621731
        ,-809855591,-1195530993,671266974,1594198024,-972236366,-1324619484,795835527,1483230225,-1050600021
        ,-1234817731,1994146192,31158534,-1731059524,-271249366,1907459465,112637215,-1614814043,-390540237
        ,2013776290,251722036,-1777751922,-519137256,2137656763,141376813,-1855689577,-429695999,1802195444
        ,476864866,-2056965928,-228458418,1812370925,453092731,-2113342271,-183516073,1706088902,314042704
        ,-1950435094,-54949764,1658658271,366619977,-1932296973,-69972891,1303535960,984961486,-1547960204
        ,-725929758,1256170817,1037604311,-1529756563,-740887301,1131014506,879679996,-1385723834,-631195440
        ,1141124467,855842277,-1442165665,-586318647,1342533948,654459306,-1106571248,-921952122,1466479909
        ,544179635,-1184443383,-832445281,1591671054,702138776,-1328506846,-942167884,1504918807,783551873
        ,-1212326853,-1061524307,-306674912,-1698712650,62317068,1957810842,-355121351,-1647151185,81470997
        ,1943803523,-480048366,-1805370492,225274430,2053790376,-468791541,-1828061283,167816743,2097651377
        ,-267414716,-2029476910,503444072,1762050814,-144550051,-2140837941,426522225,1852507879,-19653770
        ,-1982649376,282753626,1742555852,-105259153,-1900089351,397917763,1622183637,-690576408,-1580100738
        ,953729732,1340076626,-776247311,-1497606297,1068828381,1219638859,-670225446,-1358292148,906185462
        ,1090812512,-547295293,-1469587627,829329135,1181335161,-882789492,-1134132454,628085408,1382605366
        ,-871598187,-1156888829,570562233,1426400815,-977650754,-1296233688,733239954,1555261956,-1026031705
        ,-1244606671,752459403,1541320221,-1687895376,-328994266,1969922972,40735498,-1677130071,-351390145
        ,1913087877,83908371,-1782625662,-491226604,2075208622,213261112,-1831694693,-438977011,2094854071
        ,198958881,-2032938284,-237706686,1759359992,534414190,-2118248755,-155638181,1873836001,414664567
        ,-2012718362,-15766928,1711684554,285281116,-1889165569,-127750551,1634467795,376229701,-1609899400
        ,-686959890,1308918612,956543938,-1486412191,-799009033,1231636301,1047427035,-1362007478,-640263460
        ,1088359270,936918000,-1447252397,-558129467,1202900863,817233897,-1111625188,-893730166,1404277552
        ,615818150,-1160759803,-841546093,1423857449,601450431,-1285129682,-1000256840,1567103746,711928724
        ,-1274298825,-1022587231,1510334235,755167117
    };
}
